﻿#define _CRT_SECURE_NO_WARNINGS 1

#include"CentralCache.h"
#include"PageCache.h"

CentralCache CentralCache:: _sInst;

// 获取⼀个非空的span							对齐后的内存大小
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	//先查看当前spanlist中是否有未分配对象的span
	Span* it = list.Begin(); 
	while (it != list.End())
	{
		if (it->_freeList != nullptr)
		{//找到了不为空的span
			return it;
		}
		else
		{
			it = it->_next;
		}
	}

	//先把central cache的桶锁解掉，这样如果其他线程释放内存回来，不会阻塞。
	//至于其他线程申请内存，会导致额外申请多个span给central cache的同一个桶
	//"解锁→调用外部函数→重新加锁"
	//优点提高了并发度
	//缺点牺牲了一定的安全性和效率，造成资源浪费
	list._mtx.unlock();

	//走到这里说明span的内存块都分配出去了，只能找page cache要
	PageCache::GetInstance()->_pageMtx.lock();//加page cache锁
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_isUse = true;
	span->_objSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();//解page cache锁

	//对获取span切分，不需要加锁，因为这会其他线程访问不到这个span
	//计算span的大块内存的起始地址和大块内存的大小（字节数）
	char* start = (char*)(span->_pageid << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;//页的数量转化成内存大小
	char* end = start + bytes;

	//把大块内存切成自由链表并链接起来
	//尾插
	//1.先切一块下来去做头，方便尾插
	span->_freeList = start;
	start += size;
	void* tail = span->_freeList;
	while (start < end)
	{
		NextObj(tail) = start;
		tail = start;//tail=NextObj(tail);
		start += size;
	}
	//!!!此处最后一个尾结点next没有置空
	NextObj(tail) = nullptr;

	////调试	条件断点
	////中断程序，程序会在运行的地方停下来
	//int j = 0;
	//void* cur = span->_freeList;
	//while (cur)//疑似死循环	tail为空了
	//{
	//	cur = NextObj(cur);
	//	++j;
	//}
	//if (j != (bytes/size))
	//{
	//	int x = 0;
	//}

	//恢复central cache的桶锁
	//切好span以后，需要把span挂到桶里面，访问central cache,需要加锁
	list._mtx.lock();
	list.PushFront(span);
	return span;
}

// 从中⼼缓存获取⼀定数量的对象给thread cache					给几个			对齐后的内存大小
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	//加锁
	_spanLists[index]._mtx.lock();

	//获取一个非空的span
	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);//还有内存

	//从span中获取batchNum个对象
	start = span->_freeList;
	end = start;
	//end往后走batchNum-1步，指向最后一个需要的内存块起始地址
	//好处:方便最后内存块指向置空
	//情况1 batchNum比自由链表剩余内存块小，能全部申请
	//情况2	batchNum比自由链表剩余内存块大，能申请多少申请多少
	size_t i = 0;
	size_t actualNum = 1;//实际获取多少个内存块
	while (i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		i++;
		actualNum++;
	}
	span->_freeList = NextObj(end);//span自由链表重连
	NextObj(end) = nullptr;			//申请的自由链表脱离span
	span->_useCount += actualNum;

	////调试	条件断点
	//int j = 0;
	//void* cur = start;
	//while (cur)
	//{
	//	cur = NextObj(cur);
	//	++j;
	//}
	//if (j != actualNum)
	//{
	//	int x = 0;
	//}


	//解锁
	_spanLists[index]._mtx.unlock();

	return actualNum;
}

// 将⼀定数量的对象释放到span跨度					给字节数，为了算属于哪个桶
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanLists[index]._mtx.lock();
	//遍历list
	while (start)
	{
		void* next = NextObj(start);
		//找到内存块属于哪个span
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);

		//头插
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;//归还

		//说明span的切分出去的所有小块内存都回来了
		//这个span就可以再回收给page cache,pagecache可以去做前后页的合并
		if (span->_useCount == 0)
		{
			_spanLists[index].Erase(span);
			span->_freeList = nullptr;//这个链表包含了span所有内存，但是多半已经乱序了
			span->_next = nullptr;
			span->_prev = nullptr;
			//页号和页数不动	//里面的内容已经不需要了，整个span都空闲了，可以整体操作

			//把central cache中的span释放到page cache中

			//解桶锁，加page锁
			//释放span给page cache时，使用page cache的锁就可以了
			_spanLists[index]._mtx.unlock();//释放的时候其他线程可申请这个桶的其他span
											//但是其他线程此时也可能会释放span,但和GetOneSpan
											//中解锁不一样，这里是线程内存太多了，还给central cache
											//GetOneSpan获取多了浪费，而ReleaseListToSpans是归还减少冗余善后
			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();

			//加桶锁
			_spanLists[index]._mtx.lock();

		}
		
		//遍历
		start = next;
	}

	_spanLists[index]._mtx.unlock();

}